/*
 * Copyright (C) 2007-2008  Hylke van der Schaaf
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, in version 3 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package hextd.tower;

import hextd.Constants;
import hextd.creep.HexTDCreepData;
import hextd.hex.HexTile;
import hextd.util.Geometry;
import hextd.util.ParameterUtils;
import hextd.util.SiFormatter;
import hextd.wave.PathfinderWaypoints;
import hextd.wave.Wave;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.CubicCurve2D;
import java.awt.geom.GeneralPath;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.usoog.tdcore.exception.TileAlreadyHasTowerException;
import com.usoog.tdcore.exception.TileNotBuildableException;
import com.usoog.tdcore.gamegrid.ScaledCanvas;
import com.usoog.tdcore.gamemanager.GameManager;
import com.usoog.tdcore.message.MessageBuyCreep;
import com.usoog.tdcore.pathfinder.Pathfinder;
import com.usoog.tdcore.player.Player;
import com.usoog.tdcore.player.Resources;
import hextd.core.GameStateImplementation;

/**
 *
 * @author hylke
 */
public class TowerHome extends TowerBase {

	public static final Map<String, Long> price = new HashMap<String, Long>();

	static {
		price.put(Constants.RESOURCE_CREDIT, 1000l);
	}
	public static int damage = 0;
	public static double range = 0f;
	private Color transColor;
	public static String name = "Home Base";
	private GameManager gameManager;
	//
	protected int moneyTimerStart = 100;
	private int moneyTimer = moneyTimerStart;
	protected long income = 1;
	//
	private int spawnPool = 0;
	protected int spawnPoolMax = 10;
	private int spawnPoolTimerStart = 50;
	private int spawnPoolTimer = spawnPoolTimerStart;
	private Font spawnPoolFont;
	protected float[] spawnPoolLocation = {0f, 0f};
	//
	private int spawnsInQueue = 0;
	//
	private final static int HHType = 6;
	private double[] incomeIncreaseFactor = {
		100 / 100,
		100 / 100,
		100 / 100,
		100 / 100,
		100 / 100,
		100 / 100,
		100 / 100,
		100 / 100,
		100 / 100};
	private int[] creepCost = {10, 10, 10, 14, 11, 12, 12, 13, 15};
	private String[] allCreepTypes = {"rr", "bb", "yr", "gp", "ss", "hg", "prp", "ff", "hh"};
	protected String[] curCreepTypes;
	private int maxCreepType = 3;
	private int selectedCreepType = 0;
	private String[] creepCountMode = {"10"};
	private int creepCount = 1;
	//
	private String[] creepHealths = {"100", "1 000", "10 000", "100 000", "1 M", "10 M", "100 M", "1 T", "10 T", "100 T", "1 P", "10 P", "100 P"};
	private String[] creepHealthsCur;
	protected int creepHealthsMax = 0;
	private int creepHealth = 0;
	//
	private String[] allSizes = {"N", "S", "L"};
	private String[] redSizes = {"N", "S"};
	protected String[] curSizes = allSizes;
	private int selectedSize = 0;
	//
	private ArrayList<PathfinderWaypoints> paths = new ArrayList<PathfinderWaypoints>();
	private String[] pathStrings = {};
	private int selectedPath = 0;
	//
	private PanelCreepMenu p;

	protected class TowerActionSendCreep extends TowerAction {

		public int type;
		public int count;
		public int size;
		public int path;
		public int health;

		public TowerActionSendCreep(int gameTime, int type, int count, int size, int path, int health) {
			super(gameTime, "creep");
			this.type = type;
			this.count = count;
			this.size = size;
			this.path = path;
			this.health = health;
		}
	}

	public TowerHome() {
		super(FactoryTower.type.homeBase, price, damage, range);
		lineColor = new Color(255, 255, 255);
		transColor = new Color(lineColor.getRed(), lineColor.getGreen(), lineColor.getBlue(), 75);
		setCreeps();
	}

	@Override
	public void init(
			GameStateImplementation gs,
			HexTile hex,
			Player owner,
			int activateTick)
			throws TileAlreadyHasTowerException, TileNotBuildableException {
		super.init(gs, hex, owner, activateTick);
		gameManager = gameState.getGameManager();
	}

	@Override
	protected void makeButtons() {
		p = new PanelCreepMenu(this, gameState.getGameManager());
		p.setModes(1, curCreepTypes);
		p.setModes(2, curSizes);
		p.setModes(3, creepCountMode);
		p.setModes(4, pathStrings);
		p.setModes(5, creepHealthsCur);
	}

	private void setCreeps() {
		curCreepTypes = new String[maxCreepType + 1];
		System.arraycopy(allCreepTypes, 0, curCreepTypes, 0, maxCreepType + 1);

		creepHealthsCur = new String[creepHealthsMax + 1];
		System.arraycopy(creepHealths, 0, creepHealthsCur, 0, creepHealthsMax + 1);
		if (p != null) {
			p.setModes(1, curCreepTypes);
			p.setModes(5, creepHealthsCur);
		}
	}

	@Override
	protected void handleAction(TowerAction a) {
		if (a.action.equals("creep")) {
			TowerActionSendCreep asc = (TowerActionSendCreep) a;
			if (!runSendCreeps(asc.type, asc.count, asc.size, asc.path, asc.health)) {
				System.err.println("TowerHome::handleAction: Sending creeps failed.");
				System.err.println("TowerHome::handleAction: " + asc.type + ", " + asc.count + ", " + asc.size + ", " + asc.path + ", " + asc.health + ".");
				System.err.println("TowerHome::handleAction: " + maxCreepType + ", " + spawnPool + "-" + spawnsInQueue + ".");
			}
			return;
		}
		super.handleAction(a);
	}

	public synchronized void update() {
		List<Pathfinder> newPaths = gameState.getPaths();
		paths.clear();
		Player owner = this.getOwner();
		for (Pathfinder path : newPaths) {
			if (path instanceof PathfinderWaypoints && path.getOwner() == owner) {
				paths.add((PathfinderWaypoints) path);
			}
		}
		pathStrings = new String[newPaths.size()];
		for (int i = 0; i < pathStrings.length; i++) {
			pathStrings[i] = "Path " + (i + 1);
		}
		if (p != null) {
			p.setModes(4, pathStrings);
		}
	}

	public synchronized void trySendCreeps() {
		int tempCreepCount = creepCount;
		if (tempCreepCount > spawnPool - spawnsInQueue) {
			return;
		}
		if (tempCreepCount < 1) {
			return;
		}
		if (selectedCreepType > maxCreepType) {
			selectedCreepType = maxCreepType;
		}
		if (selectedCreepType == HHType && selectedSize == 2) {
			selectedSize = 0;
		}
		if (selectedPath >= paths.size()) {
			selectedPath = 0;
		}

		//System.out.println("TowerHome::trySendCreeps: count=" + creepCount + " pool=" + spawnPool + " queue=" + spawnsInQueue);

		long costs = getCreepPrice(creepHealth, selectedCreepType) * creepCount;
		Player player = getOwner();
		if (player.getResources().canQueue(Constants.RESOURCE_CREDIT, costs)) {
			MessageBuyCreep m = new MessageBuyCreep(-1, -1, creepHealth, selectedPath, selectedSize, tempCreepCount, allCreepTypes[selectedCreepType]);
			gameManager.addLocalplayerAction(m);
		}
	}

	public boolean queueSendCreeps(int activateTick, int type, int count, int size, int path, int health) {
		// These checks are time-independant.
		if (type == HHType && size == 2) {
			size = 0;
		}
		if (path < 0 || path >= paths.size()) {
			path = 0;
		}
		if (health < 0) {
			health = 0;
		}
		long costs = getCreepPrice(health, type) * count;

		Player player = getOwner();
		if (!player.getResources().queuePayment(Constants.RESOURCE_CREDIT, costs)) {
			return false;
		}
		addActionItem(new TowerActionSendCreep(activateTick, type, count, size, path, health));
		spawnsInQueue += count;

		return true;
	}

	public boolean runSendCreeps(int typeId, int count, int sizeId, int pathId, int healthId) {
		Player player = getOwner();
		// These checks are time-dependant.
		if (count < 1) {
			return false;
		}
		if (typeId > maxCreepType) {
			typeId = maxCreepType;
		}
		PathfinderWaypoints path = paths.get(pathId);
		int realPathId = gameState.getPaths().indexOf(path);
		long realHealth = 100 * (long) Math.pow(10, healthId);

		long costPerCreep = getCreepPrice(realHealth, typeId);
		long incomeIncPerCreep = getIncomeIncrease(costPerCreep, typeId);
		long totalCost = costPerCreep * count;
		long totalIncomeIncrease = incomeIncPerCreep * count;

		if (count > spawnsInQueue) {
			System.err.println("TowerHome::runSendCreeps: " + gameState.getGameTime().getTime() + " User " + getOwner() + " sending creeps that are not in queue: " + count + " > " + spawnsInQueue + "!");
		}
		//System.out.println("TowerHome::queueSendCreeps: Buying health " + realHealth + " for " + costs);

		Player tp = path.getTarget();
		if (!tp.isActive()) {
			player.getResources().unQueuePayment(Constants.RESOURCE_CREDIT, totalCost);
			return false;
		}

		spawnPool -= count;
		spawnsInQueue -= count;
		if (player.getResources().doPay(Constants.RESOURCE_CREDIT, totalCost)) {
			String[] enemies = new String[2];
			enemies[0] = "" + count;

			Map<String, Long> rewardPerCreep = new HashMap<String, Long>();
			rewardPerCreep.put(Constants.RESOURCE_CREDIT, Math.max(1, costPerCreep / 4));
			enemies[1] = "" + allSizes[sizeId] + allCreepTypes[typeId];
			Wave newWave = new Wave(realHealth, rewardPerCreep, getOwner().getId());
			newWave.setGameState(gameState);
			newWave.addEnemiesFromNames(enemies, realPathId);
			gameState.addCreeps(newWave.getEnemies());
			income += totalIncomeIncrease;
			return true;
		} else {
			System.err.println("TowerHome::runSendCreeps: " + gameState.getGameTime().getTime() + " User " + getOwner() + " "
					+ "could not afford to send creeps."
					+ " Credists: " + player.getResources().getResource(Constants.RESOURCE_CREDIT)
					+ " Queue: " + player.getResources().getQueue(Constants.RESOURCE_CREDIT)
					+ " Costs: " + totalCost);
			return false;
		}
	}

	private long getCreepPrice(long health, int type) {
		double power = 0.8;
		double base = (double) Math.pow(100, power);
		return Math.round(Math.pow(health, power) / base * creepCost[type]);
	}

	protected long getCreepPrice(int healthId, int type) {
		long health = 100l * (long) Math.pow(10, healthId);
		return getCreepPrice(health, type);
	}

	private long getIncomeIncrease(long creepPrice, int creepType) {
		return Math.round(0.5 * incomeIncreaseFactor[creepType] * Math.pow(creepPrice, 0.85));
	}

	protected void giveIncome(int gameTime) {
		Player player = getOwner();
		Resources resources = player.getResources();

		resources.doReceive(Constants.RESOURCE_CREDIT, income);
		resources.doReceive(Constants.RESOURCE_SCORE, income);
		//System.out.println("TowerHome::runTick: Tick: " + gameTime + " Income for player " + getOwner() + " = " + income);
	}

	@Override
	public void runTick(int gameTime) {
		moneyTimer--;
		if (moneyTimer <= 0) {
			moneyTimer = moneyTimerStart;
			giveIncome(gameTime);
		}

		if (spawnPool < spawnPoolMax) {
			spawnPoolTimer--;
			if (spawnPoolTimer <= 0) {
				spawnPoolTimer = spawnPoolTimerStart;
				spawnPool++;
			}

		}
	}

	@Override
	public Map<String, Long> getUpgradeCost(String mode, boolean afterQueue, Map<String, Long> target) {
		if (target == null) {
			target = new HashMap<String, Long>();
		} else {
			target.clear();
		}

		int targetLevel = level;
		if (afterQueue) {
			targetLevel += levelInQue;
		}
		int numLevels = 1;
		if (mode.equalsIgnoreCase("full")) {
			numLevels = levelMax - targetLevel;
		}

		if (targetLevel < levelMax) {
			for (int i = 0; i < numLevels; i++) {
				for (Map.Entry<String, Long> entry : price.entrySet()) {
					target.put(entry.getKey(), Math.round(0.5 * entry.getValue() * Math.pow(4, level + levelInQue + i)));
				}
			}
		}
		return target;
	}

	@Override
	protected void runUpgrade() {
		Player player = getOwner();
		if (player.getResources().doPay(getUpgradeCost("", false, null))) {
			player.getResources().doReceive(Constants.RESOURCE_BONUS, 1);
			level++;
			levelInQue--;
			spawnPoolMax += 10;
			creepCountMode[0] = "" + spawnPoolMax;
			if (p != null) {
				p.setModes(3, creepCountMode);
			}

			spawnPoolTimerStart -= 5;
			creepHealthsMax = Math.min(creepHealthsMax + 2, creepHealths.length - 1);
			if (level > 2) {
				maxCreepType = 5;
			}

			if (level > 4) {
				maxCreepType = 8;
			}

			setCreeps();
			fireTowerChangedEvent();
		} else {
			System.err.println("TowerHome::runUpgrade: " + gameState.getGameTime()
					+ " User " + getOwner()
					+ " can not upgrade tower at " + hex.getGridLocation() + ".");
		}
	}

	@Override
	protected boolean activateSetMode(String m, String v) {
		int mode = Integer.parseInt(m);
		int value = Integer.parseInt(v);
		if (mode == 1) {
			value = ParameterUtils.forceIndexBounds(curCreepTypes, value);
			selectedCreepType = value;
			if (value == HHType && curSizes != redSizes) {
				curSizes = redSizes;
				p.setModes(2, curSizes);
			}

			if (value != HHType && curSizes != allSizes) {
				curSizes = allSizes;
				p.setModes(2, curSizes);

			}

		} else if (mode == 2) {
			ParameterUtils.forceIndexBounds(curSizes, value);
			if (selectedCreepType == HHType && value == 2) {
				value = 0;
			}
			selectedSize = value;

		} else if (mode == 3) {
			if (value > spawnPoolMax) {
				value = spawnPoolMax;
			}
			if (value < 1) {
				value = 1;
			}
			creepCount = value;

		} else if (mode == 4) {
			ParameterUtils.forceIndexBounds(pathStrings, value);
			if (value >= 0 && value < pathStrings.length) {
				PathfinderWaypoints path = paths.get(selectedPath);
				path.setSelected(false);
				selectedPath = value;
				path = paths.get(selectedPath);
				path.setSelected(true);
			}

		} else if (mode == 5) {
			ParameterUtils.forceIndexBounds(creepHealthsCur, value);
			creepHealth = value;

		}
		fireTowerChangedEvent();

		return true;
	}

	@Override
	public String getStatusString() {
		String retString = "";
		retString +=
				"<h1>Home Base</h1>"
				+ "<p>"
				+ "<b>Creep Menu:</b><br>"
				+ "<table>\n";
		//double priceFactor = getCreepPrice(creepHealth);
		for (int i = 0; i < allCreepTypes.length; i++) {
			long tempPrice = getCreepPrice(creepHealth, i);
			long tempIncomeInc = getIncomeIncrease(tempPrice, i);
			retString += "<tr><td>" + HexTDCreepData.identifyEnemy(allCreepTypes[i]).toString() + ": </td>"
					+ "<td class='nr'>+" + SiFormatter.format(tempIncomeInc) + "€</td>"
					+ "<td class='nr'>" + SiFormatter.format(tempPrice) + "€</td></tr>\n";
		}

		retString += "</table>";
		retString += "<b>Creep Pool:</b> " + spawnPool + "/" + spawnPoolMax + "<br>\n";
		retString += "<b>Income:</b> " + SiFormatter.format(income) + "€<br>\n";
		retString += "</p>\n";
		long tempCosts = getCreepPrice(creepHealth, selectedCreepType) * creepCount;
		retString += "<p><br/><b>Current selection costs:</b><br/>" + SiFormatter.format(tempCosts)
				+ "€</p>";
		return retString;
	}

	public long getCosts() {
		return getCreepPrice(creepHealth, selectedCreepType) * creepCount;
	}

	@Override
	public String getDescription() {
		String retString = "";
		retString +=
				"<h1>Home Base</h1>"
				+ "<p>"
				+ "Defend this!.<br>"
				+ "</p>";
		return retString;
	}

	@Override
	public void resetScale(ScaledCanvas sc) {
		super.resetScale(sc);
		update();
	}

	@Override
	protected void generateShapes() {
		int shapeCount = 2;
		int i;
		double[][] points = Geometry.createHexCoords(paintScale);
		double[][] points2 = Geometry.createHexCoords(paintScale * 0.5f);

		shapes = new Shape[shapeCount];
		GeneralPath poly = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
		poly.append(new CubicCurve2D.Double(points[0][0], points[0][1], points2[0][ 0], points2[0][1], points2[1][0], points2[1][1], points[1][ 0], points[1][ 1]), true);
		poly.append(new CubicCurve2D.Double(points[1][0], points[1][1], points2[1][ 0], points2[1][1], points2[2][0], points2[2][1], points[2][ 0], points[2][ 1]), true);
		poly.append(new CubicCurve2D.Double(points[2][0], points[2][1], points2[2][ 0], points2[2][1], points2[3][0], points2[3][1], points[3][ 0], points[3][ 1]), true);
		poly.append(new CubicCurve2D.Double(points[3][0], points[3][1], points2[3][ 0], points2[3][1], points2[4][0], points2[4][1], points[4][0], points[4][1]), false);
		poly.append(new CubicCurve2D.Double(points[4][0], points[4][1], points2[4][ 0], points2[4][1], points2[5][0], points2[5][1], points[5][ 0], points[5][ 1]), true);
		poly.append(new CubicCurve2D.Double(points[5][0], points[5][1], points2[5][ 0], points2[5][1], points2[0][0], points2[0][1], points[0][0], points[0][1]), true);
		//poly.append(new Ellipse2D.Double(-0.6 * paintScale, -0.6 * paintScale, 1.2 * paintScale, 1.2 * paintScale), false);

		i = 0;
		shapes[i++] = new Area(poly);
		shapes[i++] = shapes[0];
		//shapes[i++] = new Ellipse2D.Double(-1.0 * strokeScale, -1.0 * strokeScale, 2.0 * strokeScale, 2.0 * strokeScale);

		i = 0;
		shapeAction = new int[shapeCount];
		shapeAction[i++] = 2;
		shapeAction[i++] = 1;
		//shapeAction[i++] = 1;

		i = 0;
		shapeColor = new Color[shapeCount];
		shapeColor[i++] = transColor;
		shapeColor[i++] = lineColor;
		//shapeColor[i++] = lineColor;

		i = 0;
		strokes = new Stroke[shapeCount];
		strokes[i++] = new BasicStroke(2.0f * strokeScale, BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL);
		strokes[i++] = strokes[0];
		//strokes[i++] = strokes[0];
		spawnPoolFont = new Font(Font.DIALOG, Font.PLAIN, (int) (0.80 * paintScale));
		spawnPoolLocation[0] = -0.5f * (float) HexTile.relativeHexWidth;
		spawnPoolLocation[1] = 0.3f * (float) paintScale;

	}

	@Override
	public void paint(Graphics2D g2, int gameTime) {
	}

	@Override
	public void paintEffects(Graphics2D g2, int gameTime) {
		super.paint(g2, gameTime);
		AffineTransform saveXform = g2.getTransform();

		g2.transform(atTranslate);
		g2.setColor(lineColor);
		g2.setFont(spawnPoolFont);
		g2.drawString("" + (spawnPool - spawnsInQueue), spawnPoolLocation[0], spawnPoolLocation[1]);

		g2.setFont(levelFont);
		g2.drawString("" + (level + 1), levelLocation[0], levelLocation[1]);

		g2.setTransform(saveXform);
	}

	@Override
	public boolean canBeSold() {
		return false;
	}

	public int getSpawnPoolSize() {
		return spawnPool - spawnsInQueue;
	}
}
